#!/usr/bin/env python

import sys
from pathlib import Path
import time
import logging
from datetime import datetime
import json
from pynrfjprog import HighLevel
import enum
import subprocess

from PySide6.QtCore import QThread, Signal, Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
from PySide6.QtGui import QPixmap
from ui_mainwindow import Ui_MainWindow

__VERSION__ = 'v1.0.1'
__AUTHOR__ = 'Gavin'
__APP_NAME__ = 'nRF BLE Programmer'

logger = logging.getLogger('GUI')
logger.setLevel(logging.INFO)

jlink_conn_snr = 0
jlink_lists = []
current_hex_file = None
current_chip = ''
current_chip_index = 0

configs_json = 'configs/settings.json'

chip_lists = []
default_chip = [{
    "id": 0,
    "name": "nrf52832_s132_ble",
    "family": "NRF52",
    "softdevice_type": "s132",
    "softdevice_req": "0x101",
    "hw_version": "52",
    "bootloader_version": "1",
    "bl_settings_version": "2",
    "application_version": "1",
    "firmware_version": "1",
    "firmware_version_next": "1",
    "application": "",
    "application_flash": "",
    "application_dfu": "",
    "private_key": "hex/private_s132.pem",
    "softdevice": "hex/s132_nrf52_7.2.0_softdevice.hex",
    "bootloader": "hex/secure_bootloader_ble_s132_pca10040.hex"
}, {
    "id": 1,
    "name": "nrf52832_s132_uart",
    "family": "NRF52",
    "softdevice_type": "s132",
    "softdevice_req": "0x101",
    "hw_version": "52",
    "bootloader_version": "1",
    "bl_settings_version": "2",
    "application_version": "11",
    "firmware_version": "11",
    "firmware_version_next": "12",
    "application": "",
    "application_flash": "",
    "application_dfu": "",
    "private_key": "hex/private_s132.pem",
    "softdevice": "hex/s132_nrf52_7.2.0_softdevice.hex",
    "bootloader": "hex/secure_bootloader_uart_s132_pca10040.hex"
}]


@enum.unique
class PROGRAM_STATE(enum.IntEnum):
    ''''''
    PROGRAM_SUCCESS = 0
    PROGRAM_FAILED = 1
    OPEN_DEVICE_FAILED = 2
    READ_NAME = 3
    READ_MAC = 4
    READ_RAM = 5
    READ_ROM = 6
    FIRMWARE_NOT_FOUND = 7
    FIRMWARE_FOUND = 8


class JlinkEraseThread(QThread):
    '''烧录线程'''
    finished = Signal()
    progress = Signal(int)
    sig1 = Signal(str)
    sig2 = Signal(int, str)

    def __init__(self, parent=None) -> None:
        super().__init__(parent)

    def log_info(self, text):
        self.sig1.emit(text)

    def run(self):
        self.jlink_erase()
        self.finished.emit()

    def jlink_erase(self):
        self.log_info('--------------------')
        self.log_info('Using nrfjprog to communicate with target.')
        # open API
        api = HighLevel.API()
        try:
            api.open()
            try:
                self.log_info(
                    f'Using nrfjprog library {api.dll_version()[0]}.{api.dll_version()[1]}.{api.dll_version()[2]}'
                )
                try:
                    print(jlink_conn_snr)
                    probe = HighLevel.DebugProbe(api, jlink_conn_snr)
                    try:
                        device_info = probe.get_device_info()
                        self.log_info(
                            f'Using J-Link Software version {probe.get_library_info().version_major}.{probe.get_library_info().version_minor}{probe.get_library_info().version_revision}'
                        )
                        self.log_info(
                            f'Segger serial: {probe.get_probe_info().serial_number}'
                        )
                        self.log_info(
                            f'Segger speed: {probe.get_probe_info().clockspeed_khz}kHz'
                        )
                        self.log_info(
                            f'Segger version: {probe.get_probe_info().firmware_string}'
                        )
                        self.log_info(
                            f'Core probed: {probe.get_probe_info().serial_number}'
                        )
                        self.log_info(
                            f'Core RAM: {device_info.ram_size // 1024}KiB')
                        self.log_info(
                            f'Core ROM: {device_info.code_size // 1024}')
                        self.log_info(
                            f'Family: {device_info.device_family.name}')
                        self.log_info(f'Model: {device_info.device_type.name}')

                        # update info
                        self.sig2.emit(PROGRAM_STATE.READ_NAME,
                                       device_info.device_type.name)
                        self.sig2.emit(
                            PROGRAM_STATE.READ_RAM,
                            '{} KB'.format(device_info.ram_size // 1024))
                        self.sig2.emit(
                            PROGRAM_STATE.READ_ROM,
                            '{} KB'.format(device_info.code_size // 1024))

                        # 读取MAC地址
                        try:
                            self.log_info('Reading Mac address:')
                            mac = list(probe.read(0x100000a4, 8))
                            mac_str = '{:0>2X}:{:0>2X}:{:0>2X}:{:0>2X}:{:0>2X}:{:0>2X}'.format(
                                (mac[5] | 0xC0), mac[4], mac[3], mac[2],
                                mac[1], mac[0])
                            self.log_info(mac_str)
                            self.sig2.emit(PROGRAM_STATE.READ_MAC, mac_str)
                        except:
                            self.log_info('Read Mac failed.')
                            self.sig2.emit(PROGRAM_STATE.READ_MAC,
                                           'Read Mac failed.')

                        try:
                            probe.erase(HighLevel.EraseAction.ERASE_ALL)
                            self.log_info(f'Erase finish.')
                            self.sig2.emit(PROGRAM_STATE.PROGRAM_SUCCESS, None)
                        except:
                            self.log_info(f'Erase failed.')
                            self.sig2.emit(PROGRAM_STATE.PROGRAM_FAILED, None)

                    finally:
                        probe.close()
                except:
                    self.log_info(f'Jlink Probe failed.')
                    self.sig2.emit(
                        PROGRAM_STATE.OPEN_DEVICE_FAILED,
                        u'An error was reported by NRFJPROG DLL: -10 EMULATOR_NOT_CONNECTED.'
                    )

            finally:
                api.close()

        except:
            self.sig2.emit(
                PROGRAM_STATE.OPEN_DEVICE_FAILED,
                u'An error was reported by NRFJPROG DLL: -2 INVALID_OPERATION.'
            )


class JlinkProgramThread(QThread):
    '''烧录线程'''
    finished = Signal()
    progress = Signal(int)
    sig1 = Signal(str)
    sig2 = Signal(int, str)

    def __init__(self, parent=None) -> None:
        super().__init__(parent)

    def log_info(self, text):
        self.sig1.emit(text)

    def run(self):
        self.jlink_program()
        self.finished.emit()

    def jlink_program(self):
        self.log_info('--------------------')
        self.log_info('Using nrfjprog to communicate with target.')
        # open API
        api = HighLevel.API()
        try:
            api.open()
            try:
                self.log_info(
                    f'Using nrfjprog library {api.dll_version()[0]}.{api.dll_version()[1]}.{api.dll_version()[2]}'
                )
                try:
                    print(jlink_conn_snr)
                    probe = HighLevel.DebugProbe(api, jlink_conn_snr)
                    try:
                        device_info = probe.get_device_info()
                        self.log_info(
                            f'Using J-Link Software version {probe.get_library_info().version_major}.{probe.get_library_info().version_minor}{probe.get_library_info().version_revision}'
                        )
                        self.log_info(
                            f'Segger serial: {probe.get_probe_info().serial_number}'
                        )
                        self.log_info(
                            f'Segger speed: {probe.get_probe_info().clockspeed_khz}kHz'
                        )
                        self.log_info(
                            f'Segger version: {probe.get_probe_info().firmware_string}'
                        )
                        self.log_info(
                            f'Core probed: {probe.get_probe_info().serial_number}'
                        )
                        self.log_info(
                            f'Core RAM: {device_info.ram_size // 1024}KiB')
                        self.log_info(
                            f'Core ROM: {device_info.code_size // 1024}')
                        self.log_info(
                            f'Family: {device_info.device_family.name}')
                        self.log_info(f'Model: {device_info.device_type.name}')

                        # update info
                        self.sig2.emit(PROGRAM_STATE.READ_NAME,
                                       device_info.device_type.name)
                        self.sig2.emit(
                            PROGRAM_STATE.READ_RAM,
                            '{} KB'.format(device_info.ram_size // 1024))
                        self.sig2.emit(
                            PROGRAM_STATE.READ_ROM,
                            '{} KB'.format(device_info.code_size // 1024))

                        # 读取MAC地址
                        try:
                            self.log_info('Reading Mac address:')
                            mac = list(probe.read(0x100000a4, 8))
                            mac_str = '{:0>2X}:{:0>2X}:{:0>2X}:{:0>2X}:{:0>2X}:{:0>2X}'.format(
                                (mac[5] | 0xC0), mac[4], mac[3], mac[2],
                                mac[1], mac[0])
                            self.log_info(mac_str)
                            self.sig2.emit(PROGRAM_STATE.READ_MAC, mac_str)
                        except:
                            self.log_info('Read Mac failed.')
                            self.sig2.emit(PROGRAM_STATE.READ_MAC,
                                           'Read Mac failed.')

                        global current_hex_file
                        if not Path(current_hex_file).exists():
                            self.log_info(
                                f'Firmware [{current_hex_file}] not found.')
                            self.sig2.emit(PROGRAM_STATE.FIRMWARE_NOT_FOUND,
                                           current_hex_file)
                        else:
                            self.log_info(
                                f'Firmware [{current_hex_file}] found.')
                            # erase and program
                            self.log_info(
                                f'Programming {current_hex_file} to device with ERASE_ALL and SYSTEM_RESET.'
                            )
                            self.sig2.emit(
                                PROGRAM_STATE.FIRMWARE_FOUND,
                                'Programming to device with ERASE_ALL and SYSTEM_RESET.'
                            )
                            program_options = HighLevel.ProgramOptions(
                                erase_action=HighLevel.EraseAction.ERASE_ALL,
                                reset=HighLevel.ResetAction.RESET_SYSTEM,
                                verify=HighLevel.VerifyAction.VERIFY_READ)

                            try:
                                probe.program(current_hex_file,
                                              program_options=program_options)
                                self.log_info(f'Program finish.')
                                self.sig2.emit(PROGRAM_STATE.PROGRAM_SUCCESS,
                                               None)
                            except:
                                self.log_info(f'Programming failed.')
                                self.sig2.emit(PROGRAM_STATE.PROGRAM_FAILED,
                                               None)

                    finally:
                        probe.close()
                except:
                    self.log_info(f'Jlink Probe failed.')
                    self.sig2.emit(
                        PROGRAM_STATE.OPEN_DEVICE_FAILED,
                        u'An error was reported by NRFJPROG DLL: -10 EMULATOR_NOT_CONNECTED.'
                    )

            finally:
                api.close()

        except:
            self.sig2.emit(
                PROGRAM_STATE.OPEN_DEVICE_FAILED,
                u'An error was reported by NRFJPROG DLL: -2 INVALID_OPERATION.'
            )


class JlinkUpdateThread(QThread):
    '''Jlink检测线程'''
    sig1 = Signal(str)
    finished = Signal()
    updated = Signal(list)

    def __init__(self, parent) -> None:
        super().__init__(parent)

    def run(self):
        self.log_info('Jlink scanning:')
        self.find_jlink()
        self.finished.emit()

    def log_info(self, text):
        self.sig1.emit(text)

    def find_jlink(self):
        with HighLevel.API() as api:
            conn_snr = api.get_connected_probes()
            self.updated.emit(conn_snr)


class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.setWindowTitle(f'{__APP_NAME__} {__VERSION__} by {__AUTHOR__}')

        self.jlink_update_state = False

        self.load_config()
        self.logger_init()
        self.init_state()
        self.update_status_bar(
            f'Application init finish. Version: {__VERSION__}\n')

    def closeEvent(self, event) -> None:
        logger.info('Bye Bye')

    def logger_info(self, info):
        timestamp = '{}> '.format(time.strftime('%X'))
        logger.info(str(timestamp + info))

    def logger_init(self):
        # save log
        fh = logging.FileHandler('log/{}'.format(
            datetime.now().strftime('%Y%m%d_%H%M%S.log')))
        fh.setFormatter(logging.Formatter("%(asctime)s - %(message)s"))
        fh.setLevel(logging.DEBUG)
        logger.addHandler(fh)

        # stdout
        ch = logging.StreamHandler()
        ch.setFormatter(
            logging.Formatter("%(asctime)s - %(levelname)s - %(message)s",
                              '%H:%M:%S'))
        ch.setLevel(logging.DEBUG)
        logger.addHandler(ch)

    def load_config(self):
        # create out/configs
        try:
            Path('log').mkdir(parents=True, exist_ok=True)
            Path('configs').mkdir(parents=True, exist_ok=True)
            Path('hex').mkdir(parents=True, exist_ok=True)
            Path('out').mkdir(parents=True, exist_ok=True)
        except:
            self.update_status_bar('Load config failed.')

        # create default setting: nrf52832
        self.create_default_settings()

    def create_default_settings(self):
        '''default: nrf52832'''
        # check if settings.json exists
        global configs_json
        if not Path(configs_json).exists():
            print(f'Creating {configs_json}...')
            with open(configs_json, 'w') as f:
                settings = {}
                data = json.loads(json.dumps(settings))

                global jlink_conn_snr
                data['jlink'] = jlink_conn_snr
                data['default'] = 0
                data['chip'] = default_chip
                json.dump(data, f, indent=4)

    def load_user_settings(self):
        global jlink_conn_snr

        # load settings
        global configs_json
        with open(configs_json, 'r') as f:
            settings = json.load(f)
            try:
                jlink_conn_snr = settings['jlink']
                id = settings['default']

                self.ui.softdevice_le.setText(
                    settings['chip'][id]['softdevice'])
                self.ui.flash_hex_le.setText(
                    settings['chip'][id]['application_flash'])
                self.ui.app_le.setText(settings['chip'][id]['application'])
                self.ui.dfu_le.setText(settings['chip'][id]['application_dfu'])

                # update chip combobox & check list
                self.ui.chip_combobox.clear()
                for i in range(len(settings['chip'])):
                    self.ui.chip_combobox.addItem(settings['chip'][i]['name'])
                    chip_lists.append(settings['chip'][i]['name'])
                self.ui.chip_combobox.setCurrentIndex(settings['default'])
                global current_chip_index
                current_chip_index = settings['default']

            except:
                self.logger_info(f'Open {configs_json} failed.')

    def init_state(self):
        self.load_user_settings()

        # signal
        self.ui.jlink_update_btn.clicked.connect(self.jlink_update_btn_handle)
        self.ui.jlink_combobox.textActivated[str].connect(
            self.jlink_combobox_activated_handle)
        self.ui.flash_btn.clicked.connect(self.flash_btn_handle)
        self.ui.erase_all_btn.clicked.connect(self.erase_all_btn_handle)
        self.ui.chip_combobox.textActivated[str].connect(
            self.chip_combobox_activated_handle)

        self.ui.app_cb.stateChanged.connect(self.app_cb_handle)
        self.ui.app_le.textChanged.connect(self.app_le_handle)
        self.ui.flash_hex_cb.stateChanged.connect(self.flash_hex_cb_handle)
        self.ui.flash_hex_le.textChanged.connect(self.flash_hex_le_handle)

        self.ui.gen_pri_key_btn.clicked.connect(self.gen_pri_key_btn_handle)
        self.ui.gen_dfu_btn.clicked.connect(self.gen_dfu_btn_handle)
        self.ui.gen_flash_hex_btn.clicked.connect(
            self.gen_flash_hex_btn_handle)

        self.ui.app_load.clicked.connect(self.app_load_handle)
        self.ui.flash_hex_load.clicked.connect(self.flash_hex_load_handle)

        # update jlink
        self.jlink_update_btn_handle()

    def update_status_bar(self, value):
        self.statusBar().showMessage(value)
        self.logger_info(value)

    def update_user_setting(self, s):
        if Path(configs_json).exists():
            with open(configs_json, 'r') as f:
                user_setting = json.load(f)
                for key in s.keys():
                    if key == 'jlink':
                        user_setting['jlink'] = s[key]
                    elif key == 'default':
                        user_setting['default'] = s[key]
                    else:
                        user_setting['chip'][current_chip_index][key] = s[key]
                json.dump(user_setting, open(configs_json, 'w'), indent=4)

    #---------------------------------------------
    # jlink
    def jlink_update_btn_handle(self):
        if self.jlink_update_state:
            self.logger_info('Updating, please wait...')
            return

        # 设置状态
        self.jlink_update_state = True
        self.ui.jlink_update_btn.setEnabled(False)
        self.update_status_bar(u'Searching Jlink...')

        # 开启线程查找Jlink
        self.jlink_update_start()

    def jlink_update_start(self):
        ''''''
        self.t = JlinkUpdateThread(self)
        self.t.finished.connect(self.t.deleteLater)
        self.t.finished.connect(self.jlink_update_finished_thread_handle)
        self.t.sig1.connect(self.logger_info)
        self.t.updated.connect(self.jlink_updated_thread_handle)
        self.t.start()

    def jlink_updated_thread_handle(self, lists):
        self.ui.jlink_combobox.clear()
        self.logger_info('--------------------')
        for idx, value in enumerate(lists):
            self.logger_info(f'\t{idx}/{len(lists)}, ' + str(value))
            self.ui.jlink_combobox.addItem(str(value))
        self.logger_info('--------------------')
        global jlink_lists
        jlink_lists = lists

    def jlink_update_finished_thread_handle(self):
        self.jlink_update_state = False
        self.logger_info('Jlink Scan Finish.')

        if len(jlink_lists) == 0:
            self.update_status_bar('No Jlink Found.')
            self.ui.jlink_update_btn.setEnabled(True)
            return

        # display jlink info
        global jlink_conn_snr
        selected_jlink = 0
        try:
            selected_jlink = jlink_lists.index(jlink_conn_snr)
        except:
            pass

        # set current index
        self.logger_info(f'Current selected: {jlink_lists[selected_jlink]}')
        self.ui.jlink_combobox.setCurrentIndex(selected_jlink)
        jlink_conn_snr = jlink_lists[selected_jlink]

        # update settings.json
        user_setting = {'jlink': jlink_conn_snr}
        self.update_user_setting(user_setting)

        # enable button
        self.ui.jlink_update_btn.setEnabled(True)
        self.update_status_bar(f'Jlink updated: {jlink_conn_snr}')

    def jlink_combobox_activated_handle(self, text):
        self.logger_info(f'Jlink selected: {text}')
        global jlink_conn_snr
        jlink_conn_snr = int(text)
        # update setting
        if Path('config/user_setting.json').exists():
            with open('config/user_setting.json', 'r') as f:
                user_setting = json.load(f)
                user_setting['jlink'] = jlink_conn_snr
                json.dump(user_setting, open('config/user_setting.json', 'w'))

    def chip_combobox_activated_handle(self, text):
        global current_chip_index
        current_chip_index = self.ui.chip_combobox.currentIndex()
        user_setting = {'default': current_chip_index}
        self.update_user_setting(user_setting)
        self.load_user_settings()

    def jlink_program_start(self):
        ''''''
        self.t1 = JlinkProgramThread(self)
        self.t1.finished.connect(self.t1.deleteLater)
        self.t1.finished.connect(self.jlink_program_finished_thread_handle)
        self.t1.progress.connect(lambda x: self.ui.progressBar.setValue(x))
        self.t1.sig1.connect(self.logger_info)
        self.t1.sig2.connect(self.jlink_program_result_thread_handle)
        self.t1.start()

        self.ui.state_icon.setPixmap(QPixmap(u":/running/running.png"))
        self.ui.progressBar.setValue(1)
        self.update_status_bar(u'Connecting to device ...')

    def jlink_program_finished_thread_handle(self):
        # self.update_status_bar('Programe Finish.')
        # # update icon
        pass

    def jlink_program_result_thread_handle(self, index, value=None):
        if index == PROGRAM_STATE.PROGRAM_SUCCESS:
            self.ui.state_icon.setPixmap(QPixmap(u":/OK/OK.png"))
            self.update_status_bar('Programe Finish.')
            self.ui.progressBar.setValue(100)
        elif index == PROGRAM_STATE.PROGRAM_FAILED:
            self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
            self.update_status_bar('Programe Failed.')
            self.ui.progressBar.setValue(100)
        elif index == PROGRAM_STATE.OPEN_DEVICE_FAILED:
            self.logger_info(
                f'Errorcode: CouldNotOpenDevice (0x4) Lowlevel error: JLINKARM_DLL_ERROR (ffffff9a).'
            )
            self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
            self.update_status_bar(
                u'Errorcode: CouldNotOpenDevice (0x4) Lowlevel error: JLINKARM_DLL_ERROR (ffffff9a).'
            )
            self.ui.progressBar.setValue(100)
        elif index == PROGRAM_STATE.READ_MAC:
            self.ui.progressBar.setValue(60)
            self.ui.mac_le.setText(value)
        elif index == PROGRAM_STATE.READ_NAME:
            self.ui.progressBar.setValue(21)
            self.ui.model_le.setText(value)
        elif index == PROGRAM_STATE.READ_RAM:
            self.ui.progressBar.setValue(25)
            self.ui.ram_le.setText(value)
        elif index == PROGRAM_STATE.READ_ROM:
            self.ui.progressBar.setValue(30)
            self.ui.rom_le.setText(value)
        elif index == PROGRAM_STATE.FIRMWARE_NOT_FOUND:
            self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
            self.ui.progressBar.setValue(100)
            self.update_status_bar(u'hex file not found.')
        elif index == PROGRAM_STATE.FIRMWARE_FOUND:
            self.update_status_bar(value)

    def jlink_erase_start(self):
        ''''''
        self.t2 = JlinkEraseThread(self)
        self.t2.finished.connect(self.t2.deleteLater)
        self.t2.progress.connect(lambda x: self.ui.progressBar.setValue(x))
        self.t2.sig1.connect(self.logger_info)
        self.t2.sig2.connect(self.jlink_erase_result_thread_handle)
        self.t2.start()

        self.ui.state_icon.setPixmap(QPixmap(u":/running/running.png"))
        self.ui.progressBar.setValue(1)
        self.update_status_bar(u'Connecting to device ...')

    def jlink_erase_result_thread_handle(self, index, value=None):
        if index == PROGRAM_STATE.PROGRAM_SUCCESS:
            self.ui.state_icon.setPixmap(QPixmap(u":/OK/OK.png"))
            self.update_status_bar('Erase Finish.')
            self.ui.progressBar.setValue(100)
        elif index == PROGRAM_STATE.PROGRAM_FAILED:
            self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
            self.update_status_bar('Erase Failed.')
            self.ui.progressBar.setValue(100)
        elif index == PROGRAM_STATE.OPEN_DEVICE_FAILED:
            self.logger_info(
                f'Errorcode: CouldNotOpenDevice (0x4) Lowlevel error: JLINKARM_DLL_ERROR (ffffff9a).'
            )
            self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
            self.update_status_bar(
                u'Errorcode: CouldNotOpenDevice (0x4) Lowlevel error: JLINKARM_DLL_ERROR (ffffff9a).'
            )
            self.ui.progressBar.setValue(100)
        elif index == PROGRAM_STATE.READ_MAC:
            self.ui.progressBar.setValue(60)
            self.ui.mac_le.setText(value)
        elif index == PROGRAM_STATE.READ_NAME:
            self.ui.progressBar.setValue(21)
            self.ui.model_le.setText(value)
        elif index == PROGRAM_STATE.READ_RAM:
            self.ui.progressBar.setValue(25)
            self.ui.ram_le.setText(value)
        elif index == PROGRAM_STATE.READ_ROM:
            self.ui.progressBar.setValue(30)
            self.ui.rom_le.setText(value)

    def flash_btn_handle(self):
        # check jlink
        global jlink_conn_snr
        if jlink_conn_snr == 0:
            self.update_status_bar('Can not found jlink.')
            self.jlink_update_btn_handle()
            return

        # check file
        if current_hex_file is None:
            self.update_status_bar('No hex file selected.')
            return

        # clear info
        self.ui.model_le.clear()
        self.ui.mac_le.clear()
        self.ui.ram_le.clear()
        self.ui.rom_le.clear()

        self.jlink_program_start()

    def erase_all_btn_handle(self):
        global jlink_conn_snr
        if jlink_conn_snr == 0:
            self.update_status_bar('Can not found jlink.')
            self.jlink_update_btn_handle()
            return
        self.jlink_erase_start()

    def flash_hex_cb_handle(self):
        global current_hex_file
        if self.ui.flash_hex_cb.checkState():
            self.ui.app_cb.setCheckState(Qt.CheckState.Unchecked)
            if self.ui.flash_hex_le.text():
                current_hex_file = self.ui.flash_hex_le.text()
        else:
            current_hex_file = None

    def app_cb_handle(self):
        global current_hex_file
        if self.ui.app_cb.checkState():
            self.ui.flash_hex_cb.setCheckState(Qt.CheckState.Unchecked)
            if self.ui.app_le.text():
                current_hex_file = self.ui.app_le.text()
        else:
            current_hex_file = None

    def flash_hex_le_handle(self):
        global current_hex_file
        if self.ui.flash_hex_cb.checkState():
            if self.ui.flash_hex_le.text():
                current_hex_file = self.ui.flash_hex_le.text()
            else:
                current_hex_file = None

        # save to setting
        user_setting = {
            'application_flash': '{}'.format(self.ui.flash_hex_le.text())
        }
        self.update_user_setting(user_setting)

    def app_le_handle(self):
        global current_hex_file
        if self.ui.app_cb.checkState():
            if self.ui.app_le.text():
                current_hex_file = self.ui.app_le.text()
            else:
                current_hex_file = None
        # save to setting
        user_setting = {'application': '{}'.format(self.ui.app_le.text())}
        self.update_user_setting(user_setting)

    def gen_pri_key_btn_handle(self):
        self.update_status_bar('!!TODO!!')

    def gen_dfu_btn_handle(self):
        # load json
        if not Path(configs_json).exists():
            self.update_status_bar(f'{configs_json} not found!!')
            self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
            return

        with open(configs_json, 'r') as f:
            settings = json.load(f)
            self.ui.state_icon.setPixmap(QPixmap(u":/running/running.png"))
            self.ui.progressBar.setValue(0)

            # delete output
            global current_chip_index
            if settings['chip'][current_chip_index]['application_dfu']:
                Path('{}'.format(settings['chip'][current_chip_index]
                                 ['application_dfu'])).unlink(missing_ok=True)

            # check application, private.pem
            if not settings['chip'][current_chip_index]['application']:
                self.update_status_bar(
                    'Not found hex in settings, generate DFU firmware failed.')
                self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
                self.ui.progressBar.setValue(100)
                return
            else:
                if not Path('{}'.format(settings['chip'][current_chip_index]
                                        ['application'])).exists():
                    self.update_status_bar(
                        'Not found hex, generate DFU firmware failed.')
                    self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
                    self.ui.progressBar.setValue(100)
                    return

            if not settings['chip'][current_chip_index]['private_key']:
                self.update_status_bar(
                    'Not found private key, generate DFU firmware failed.')
                self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
                self.ui.progressBar.setValue(100)
                return

            self.ui.app_le.clear()
            self.ui.softdevice_le.clear()
            self.ui.dfu_le.clear()

            out_file = 'out/app_dfu_fw_{}_v{}.zip'.format(
                settings['chip'][current_chip_index]['softdevice_type'],
                settings['chip'][current_chip_index]['firmware_version_next'])
            exe_cmd = "nrfutil pkg generate --application {} --application-version {} --hw-version {} --sd-req {} --key-file {} {}".format(
                settings['chip'][current_chip_index]['application'],
                settings['chip'][current_chip_index]['application_version'],
                settings['chip'][current_chip_index]['hw_version'],
                settings['chip'][current_chip_index]['softdevice_req'],
                settings['chip'][current_chip_index]['private_key'], out_file)

            result = subprocess.run(exe_cmd,
                                    shell=True,
                                    capture_output=True,
                                    text=True)
            if result.returncode == 0:
                # update settings
                settings['chip'][current_chip_index][
                    'application_dfu'] = out_file
                json.dump(settings, open(configs_json, 'w'), indent=4)

                self.update_status_bar('Generate {} OK.'.format(
                    settings['chip'][current_chip_index]['application_dfu']))
                self.ui.state_icon.setPixmap(QPixmap(u":/OK/OK.png"))
                self.ui.progressBar.setValue(100)
                # display
                self.ui.dfu_le.setText('{}'.format(
                    settings['chip'][current_chip_index]['application_dfu']))
                self.ui.softdevice_le.setText('{}'.format(
                    settings['chip'][current_chip_index]['softdevice']))
                self.ui.app_le.setText('{}'.format(
                    settings['chip'][current_chip_index]['application']))
            else:
                self.update_status_bar('Generate {} failed.'.format(
                    settings['chip'][current_chip_index]['application_dfu']))
                self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
                self.ui.progressBar.setValue(100)

    def gen_flash_hex_btn_handle(self):
        '''
        1.create settings
        2.generate flash firmware
        '''
        if not Path(configs_json).exists():
            self.update_status_bar(f'{configs_json} not found!!')
            self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
            return

        with open(configs_json, 'r') as f:
            settings = json.load(f)
            self.ui.state_icon.setPixmap(QPixmap(u":/running/running.png"))
            self.ui.progressBar.setValue(0)

            # delete output
            if settings['chip'][current_chip_index]['application_flash']:
                Path(
                    '{}'.format(settings['chip'][current_chip_index]
                                ['application_flash'])).unlink(missing_ok=True)

            self.ui.app_le.clear()
            self.ui.softdevice_le.clear()
            self.ui.flash_hex_le.clear()

            # 1.create settings
            out_file = 'out/settings.hex'
            exe_cmd = "nrfutil settings generate --family {} --application {} --application-version {} --bootloader-version {} --bl-settings-version {} {}".format(
                settings['chip'][current_chip_index]['family'],
                settings['chip'][current_chip_index]['application'],
                settings['chip'][current_chip_index]['application_version'],
                settings['chip'][current_chip_index]['bootloader_version'],
                settings['chip'][current_chip_index]['bl_settings_version'],
                out_file)

            result = subprocess.run(exe_cmd,
                                    shell=True,
                                    capture_output=True,
                                    text=True)

            if result.returncode == 0:
                self.update_status_bar('Generate setting.hex OK.')
                self.ui.progressBar.setValue(50)
            else:
                self.update_status_bar('Generate setting.hex failed.')
                self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
                self.ui.progressBar.setValue(100)
                return

            # 2.generate flash firmware
            merge_cmd = 'mergehex -m {} {} {} -o out/_merged.hex'.format(
                settings['chip'][current_chip_index]['softdevice'],
                settings['chip'][current_chip_index]['bootloader'],
                settings['chip'][current_chip_index]['application'])
            # self.logger_info(f'merge cmd[1]: {merge_cmd}')
            ret = subprocess.run(merge_cmd,
                                 shell=True,
                                 capture_output=True,
                                 text=True)
            if ret.returncode == 0:
                self.ui.state_icon.setPixmap(QPixmap(u":/OK/OK.png"))
                self.ui.progressBar.setValue(100)
            else:
                self.update_status_bar('Generate {} failed.'.format(
                    settings['chip'][current_chip_index]['application_flash']))
                self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
                self.ui.progressBar.setValue(100)
                return

            out_file = 'out/app_flash_fw_{}_v{}.hex'.format(
                settings['chip'][current_chip_index]['softdevice_type'],
                settings['chip'][current_chip_index]['firmware_version'])
            merge_cmd = 'mergehex.exe -m out/_merged.hex out/settings.hex -o {}'.format(
                out_file)
            # self.logger_info(f'merge cmd[2]: {merge_cmd}')
            ret = subprocess.run(merge_cmd,
                                 shell=True,
                                 capture_output=True,
                                 text=True)
            if ret.returncode == 0:
                # update settings
                settings['chip'][current_chip_index][
                    'application_flash'] = out_file
                json.dump(settings, open(configs_json, 'w'), indent=4)

                self.update_status_bar('Generate {} OK.'.format(
                    settings['chip'][current_chip_index]['application_flash']))
                self.ui.state_icon.setPixmap(QPixmap(u":/OK/OK.png"))
                self.ui.progressBar.setValue(100)
                # display
                self.ui.flash_hex_le.setText('{}'.format(
                    settings['chip'][current_chip_index]['application_flash']))
                self.ui.app_le.setText('{}'.format(
                    settings['chip'][current_chip_index]['application']))
                self.ui.softdevice_le.setText('{}'.format(
                    settings['chip'][current_chip_index]['softdevice']))

                # clear
                Path('out/_merged.hex').unlink(missing_ok=True)
                Path('out/settings.hex').unlink(missing_ok=True)
            else:
                self.update_status_bar('Generate {} failed.'.format(
                    settings['chip'][current_chip_index]['application_flash']))
                self.ui.state_icon.setPixmap(QPixmap(u":/error/error.png"))
                self.ui.progressBar.setValue(100)
                return

    def app_load_handle(self):
        path = QFileDialog.getOpenFileName(self, 'Open hex file', '',
                                           'hex file(*.hex)')
        if path != ('', ''):
            self.logger_info(path[0])
            self.ui.app_le.setText(path[0])
            settings = {'application': path[0]}
            self.update_user_setting(settings)
            # save path
            # global settings_file
            # with open(settings_file, 'r') as f:
            #     settings = json.load(f)
            #     settings['application'] = path[0]
            #     json.dump(settings, open(settings_file, 'w'), indent=4)

    def flash_hex_load_handle(self):
        path = QFileDialog.getOpenFileName(self, 'Open hex file', '',
                                           'hex file(*.hex)')
        if path != ('', ''):
            self.logger_info(path[0])
            self.ui.flash_hex_le.setText(path[0])

            # save path
            global settings_file
            with open(settings_file, 'r') as f:
                settings = json.load(f)
                settings['application_flash'] = path[0]
                json.dump(settings, open(settings_file, 'w'), indent=4)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())